/**
 * @ignore
 * dom-data
 * @author lifesinger@gmail.com, yiminghe@gmail.com
 */

var util = require('util');
/*jshint eqeqeq:false*/
// cannot use === for window in ie8
var Dom = require('./api');
var win = window,
    EXPANDO = '_ks_data_' + util.now(), // 让每一份 kissy 的 expando 都不同
    dataCache = {}, // 存储 node 节点的 data
    winDataCache = {}, // 避免污染全局
// The following elements throw uncatchable exceptions if you
// attempt to add expando properties to them.
    noData = {
        applet: 1,
        object: 1,
        embed: 1
    };
var commonOps = {
    hasData: function (cache, name) {
        if (cache) {
            if (name !== undefined) {
                if (name in cache) {
                    return true;
                }
            } else if (!util.isEmptyObject(cache)) {
                return true;
            }
        }
        return false;
    }
};

var objectOps = {
    hasData: function (ob, name) {
        // 只判断当前窗口，iframe 窗口内数据直接放入全局变量
        if (ob == win) {
            return objectOps.hasData(winDataCache, name);
        }
        // 直接建立在对象内
        var thisCache = ob[EXPANDO];
        return commonOps.hasData(thisCache, name);
    },

    data: function (ob, name, value) {
        if (ob == win) {
            return objectOps.data(winDataCache, name, value);
        }
        var cache = ob[EXPANDO];
        if (value !== undefined) {
            cache = ob[EXPANDO] = ob[EXPANDO] || {};
            cache[name] = value;
        } else {
            if (name !== undefined) {
                return cache && cache[name];
            } else {
                cache = ob[EXPANDO] = ob[EXPANDO] || {};
                return cache;
            }
        }
    },
    removeData: function (ob, name) {
        if (ob == win) {
            return objectOps.removeData(winDataCache, name);
        }
        var cache = ob[EXPANDO];
        if (name !== undefined) {
            delete cache[name];
            if (util.isEmptyObject(cache)) {
                objectOps.removeData(ob);
            }
        } else {
            try {
                // ob maybe window in iframe
                // ie will throw error
                delete ob[EXPANDO];
            } catch (e) {
                ob[EXPANDO] = undefined;
            }
        }
    }
};

var domOps = {
    hasData: function (elem, name) {
        var key = elem[EXPANDO];
        if (!key) {
            return false;
        }
        var thisCache = dataCache[key];
        return commonOps.hasData(thisCache, name);
    },

    data: function (elem, name, value) {
        if (noData[elem.nodeName.toLowerCase()]) {
            return undefined;
        }
        var key = elem[EXPANDO], cache;
        if (!key) {
            // 根本不用附加属性
            if (name !== undefined &&
                value === undefined) {
                return undefined;
            }
            // 节点上关联键值也可以
            key = elem[EXPANDO] = util.guid();
        }
        cache = dataCache[key];
        if (value !== undefined) {
            // 需要新建
            cache = dataCache[key] = dataCache[key] || {};
            cache[name] = value;
        } else {
            if (name !== undefined) {
                return cache && cache[name];
            } else {
                // 需要新建
                cache = dataCache[key] = dataCache[key] || {};
                return cache;
            }
        }
    },

    removeData: function (elem, name) {
        var key = elem[EXPANDO], cache;
        if (!key) {
            return;
        }
        cache = dataCache[key];
        if (name !== undefined) {
            delete cache[name];
            if (util.isEmptyObject(cache)) {
                domOps.removeData(elem);
            }
        } else {
            delete dataCache[key];
            try {
                delete elem[EXPANDO];
            } catch (e) {
                elem[EXPANDO] = undefined;
            }
            if (elem.removeAttribute) {
                elem.removeAttribute(EXPANDO);
            }
        }
    }
};

util.mix(Dom,
    /**
     * @override KISSY.DOM
     * @class
     * @singleton
     */
    {

        __EXPANDO: EXPANDO,

        /**
         * Determine whether an element has any data or specified data name associated with it.
         * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
         * @param {String} [name] A string naming the piece of data to set.
         * @return {Boolean}
         */
        hasData: function (selector, name) {
            var ret = false,
                elems = Dom.query(selector);
            for (var i = 0; i < elems.length; i++) {
                var elem = elems[i];
                if (elem.nodeType) {
                    ret = domOps.hasData(elem, name);
                } else {
                    // window
                    ret = objectOps.hasData(elem, name);
                }
                if (ret) {
                    return ret;
                }
            }
            return ret;
        },

        /**
         * If name set and data unset Store arbitrary data associated with the specified element. Returns undefined.
         * or
         * If name set and data unset returns value at named data store for the element
         * or
         * If name unset and data unset returns the full data store for the element.
         * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
         * @param {String} [name] A string naming the piece of data to set.
         * @param {*} [data] The new data value.
         * @return {Object|undefined}
         */
        data: function (selector, name, data) {
            var elems = Dom.query(selector), elem = elems[0];

            // supports hash
            if (typeof name === 'object') {
                for (var k in name) {
                    Dom.data(elems, k, name[k]);
                }
                return undefined;
            }

            // getter
            if (data === undefined) {
                if (elem) {
                    if (elem.nodeType) {
                        return domOps.data(elem, name);
                    } else {
                        // window
                        return objectOps.data(elem, name);
                    }
                }
            } else {
                for (var i = elems.length - 1; i >= 0; i--) {
                    elem = elems[i];
                    if (elem.nodeType) {
                        domOps.data(elem, name, data);
                    } else {
                        // window
                        objectOps.data(elem, name, data);
                    }
                }
            }
            return undefined;
        },

        /**
         * Remove a previously-stored piece of data from matched elements.
         * or
         * Remove all data from matched elements if name unset.
         * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
         * @param {String} [name] A string naming the piece of data to delete.
         */
        removeData: function (selector, name) {
            var els = Dom.query(selector), elem, i;
            for (i = els.length - 1; i >= 0; i--) {
                elem = els[i];
                if (elem.nodeType) {
                    domOps.removeData(elem, name);
                } else {
                    // window
                    objectOps.removeData(elem, name);
                }
            }
        },
        /**
         * clean data from element
         * @param {HTMLElement[]|String|HTMLElement} selector Matched elements
         * @param {Boolean} deep whether clean descendant nodes
         */
        cleanData: function (selector, deep) {
            var els = Dom.query(selector), elem, i;
            var DOMEvent = module.require('event/dom');
            for (i = els.length - 1; i >= 0; i--) {
                elem = els[i];
                if (elem.nodeType) {
                    var descendants = deep && util.makeArray(elem.getElementsByTagName('*')) || [];
                    descendants.push(elem);
                    for (var j = 0, len = descendants.length; j < len; j++) {
                        domOps.removeData(descendants[j]);
                    }
                    if (DOMEvent) {
                        DOMEvent.detach(descendants);
                    }
                } else {
                    // window
                    objectOps.removeData(elem);
                }
            }
        }
    });

/*
 yiminghe@gmail.com：2011-05-31
 - 分层，节点和普通对象分开处理
 */